//
// Created by syshe on 2022/3/21.
//

#ifndef GENERAL_OCR_REGISTRYFACTORY_H
#define GENERAL_OCR_REGISTRYFACTORY_H

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>

#include "Registry/utils.h"
#include "utils/macros.h"

namespace {
};

namespace CJLU{
    template <class SrcType, class ObjectPtrType, class... Args>
    class Registry {
        public:
            typedef std::function<ObjectPtrType(Args...)> Creator;

            Registry(bool warning = true)
                    : registry_(), priority_(), terminate_(true), warning_(warning) {}

            void Register(
                    const SrcType& key,
                    Creator creator,
                    const RegistryPriority priority = REGISTRY_DEFAULT) {
                std::lock_guard<std::mutex> lock(register_mutex_);
                // The if statement below is essentially the same as the following line:
                // CHECK_EQ(registry_.count(key), 0) << "Key " << key
                //                                   << " registered twice.";
                // However, CHECK_EQ depends on google logging, and since registration is
                // carried out at static initialization time, we do not want to have an
                // explicit dependency on glog's initialization function.
                if (registry_.count(key) != 0) {
                    auto cur_priority = priority_[key];
                    if (priority > cur_priority) {
#ifdef DEBUG
                        std::string warn_msg = "Overwriting already registered item for key " + KeyStrRepr(key);
                        fprintf(stderr, "%s\n", warn_msg.c_str());
#endif
                        registry_[key] = creator;
                        priority_[key] = priority;
                    } else if (priority == cur_priority) {
                        std::string err_msg =
                                "Key already registered with the same priority: " + KeyStrRepr(key);
                        fprintf(stderr, "%s\n", err_msg.c_str());
                        if (terminate_) {
                            std::exit(1);
                        } else {
                            throw std::runtime_error(err_msg);
                        }
                    } else if (warning_) {
                        std::string warn_msg =
                                "Higher priority item already registered, skipping registration of " +
                                KeyStrRepr(key);
                        fprintf(stderr, "%s\n", warn_msg.c_str());
                    }
                } else {
                    registry_[key] = creator;
                    priority_[key] = priority;
                }
            }

            void Register(
                    const SrcType& key,
                    Creator creator,
                    const std::string& help_msg,
                    const RegistryPriority priority = REGISTRY_DEFAULT) {
                Register(key, creator, priority);
                help_message_[key] = help_msg;
            }

            inline bool Has(const SrcType& key) {
                return (registry_.count(key) != 0);
            }

            ObjectPtrType Create(const SrcType& key, Args... args) {
                if (registry_.count(key) == 0) {
                    // Returns nullptr if the key is not registered.
                    return nullptr;
                }
                return registry_[key](args...);
            }

            /**
             * Returns the keys currently registered as a std::vector.
             */
            std::vector<SrcType> Keys() const {
                std::vector<SrcType> keys;
                for (const auto& it : registry_) {
                    keys.push_back(it.first);
                }
                return keys;
            }

            inline const std::unordered_map<SrcType, std::string>& HelpMessage() const {
                return help_message_;
            }

            const char* HelpMessage(const SrcType& key) const {
                auto it = help_message_.find(key);
                if (it == help_message_.end()) {
                    return nullptr;
                }
                return it->second.c_str();
            }

            // Used for testing, if terminate is unset, Registry throws instead of
            // calling std::exit
            void SetTerminate(bool terminate) {
                terminate_ = terminate;
            }

        private:
            std::unordered_map<SrcType, Creator> registry_;
            std::unordered_map<SrcType, RegistryPriority> priority_;
            bool terminate_;
            const bool warning_;
            std::unordered_map<SrcType, std::string> help_message_;
            std::mutex register_mutex_;

            CJLU_DISABLE_COPY_AND_ASSIGN(Registry);
    };


    template <class SrcType, class ObjectPtrType, class... Args>
    class Registerer {
        public:
            explicit Registerer(
                    const SrcType& key,
                    Registry<SrcType, ObjectPtrType, Args...>* registry,
                    typename Registry<SrcType, ObjectPtrType, Args...>::Creator creator,
                    const std::string& help_msg = "") {
                registry->Register(key, creator, help_msg);
            }

            explicit Registerer(
                    const SrcType& key,
                    const RegistryPriority priority,
                    Registry<SrcType, ObjectPtrType, Args...>* registry,
                    typename Registry<SrcType, ObjectPtrType, Args...>::Creator creator,
                    const std::string& help_msg = "") {
                registry->Register(key, creator, help_msg, priority);
            }

            template <class DerivedType>
            static ObjectPtrType DefaultCreator(Args... args) {
                return ObjectPtrType(new DerivedType(args...));
            }
    };

/**
 * CJLU_DECLARE_TYPED_REGISTRY is a macro that expands to a function
 * declaration, as well as creating a convenient typename for its corresponding
 * registerer.
 */
// Note on CJLU_IMPORT and CJLU_EXPORT below: we need to explicitly mark DECLARE
// as import and DEFINE as export, because these registry macros will be used
// in downstream shared libraries as well, and one cannot use *_API - the API
// macro will be defined on a per-shared-library basis. Semantically, when one
// declares a typed registry it is always going to be IMPORT, and when one
// defines a registry (which should happen ONLY ONCE and ONLY IN SOURCE FILE),
// the instantiation unit is always going to be exported.
//
// The only unique condition is when in the same file one does DECLARE and
// DEFINE - in Windows compilers, this generates a warning that dllimport and
// dllexport are mixed, but the warning is fine and linker will be properly
// exporting the symbol. Same thing happens in the gflags flag declaration and
// definition caes.
#define CJLU_DECLARE_TYPED_REGISTRY(                                        \
    RegistryName, SrcType, ObjectType, PtrType, ...)                       \
  CJLU_IMPORT ::CJLU::Registry<SrcType, PtrType<ObjectType>, ##__VA_ARGS__>* \
  RegistryName();                                                          \
  typedef ::CJLU::Registerer<SrcType, PtrType<ObjectType>, ##__VA_ARGS__>   \
      Registerer##RegistryName

#define CJLU_DEFINE_TYPED_REGISTRY(                                         \
    RegistryName, SrcType, ObjectType, PtrType, ...)                       \
  CJLU_EXPORT ::CJLU::Registry<SrcType, PtrType<ObjectType>, ##__VA_ARGS__>* \
  RegistryName() {                                                         \
    static ::CJLU::Registry<SrcType, PtrType<ObjectType>, ##__VA_ARGS__>*   \
        registry = new ::CJLU::                                             \
            Registry<SrcType, PtrType<ObjectType>, ##__VA_ARGS__>();       \
    return registry;                                                       \
  }

#define CJLU_DEFINE_TYPED_REGISTRY_WITHOUT_WARNING(                            \
    RegistryName, SrcType, ObjectType, PtrType, ...)                          \
  CJLU_EXPORT ::CJLU::Registry<SrcType, PtrType<ObjectType>, ##__VA_ARGS__>*    \
  RegistryName() {                                                            \
    static ::CJLU::Registry<SrcType, PtrType<ObjectType>, ##__VA_ARGS__>*      \
        registry =                                                            \
            new ::CJLU::Registry<SrcType, PtrType<ObjectType>, ##__VA_ARGS__>( \
                false);                                                       \
    return registry;                                                          \
  }

// Note(Yangqing): The __VA_ARGS__ below allows one to specify a templated
// creator with comma in its templated arguments.
#define CJLU_REGISTER_TYPED_CREATOR(RegistryName, key, ...)                  \
  static Registerer##RegistryName CJLU_ANONYMOUS_VARIABLE(g_##RegistryName)( \
      key, RegistryName(), ##__VA_ARGS__);

#define CJLU_REGISTER_TYPED_CREATOR_WITH_PRIORITY(                           \
    RegistryName, key, priority, ...)                                       \
  static Registerer##RegistryName CJLU_ANONYMOUS_VARIABLE(g_##RegistryName)( \
      key, priority, RegistryName(), ##__VA_ARGS__);

#define CJLU_REGISTER_TYPED_CLASS(RegistryName, key, ...)                    \
  static Registerer##RegistryName CJLU_ANONYMOUS_VARIABLE(g_##RegistryName)( \
      key,                                                                  \
      RegistryName(),                                                       \
      Registerer##RegistryName::DefaultCreator<__VA_ARGS__>,                \
      ::CJLU::demangle_type<__VA_ARGS__>());

#define CJLU_REGISTER_TYPED_CLASS_WITH_PRIORITY(                             \
    RegistryName, key, priority, ...)                                       \
  static Registerer##RegistryName CJLU_ANONYMOUS_VARIABLE(g_##RegistryName)( \
      key,                                                                  \
      priority,                                                             \
      RegistryName(),                                                       \
      Registerer##RegistryName::DefaultCreator<__VA_ARGS__>,                \
      ::CJLU::demangle_type<__VA_ARGS__>());

// CJLU_DECLARE_REGISTRY and CJLU_DEFINE_REGISTRY are hard-wired to use
// std::string as the key type, because that is the most commonly used cases.
#define CJLU_DECLARE_REGISTRY(RegistryName, ObjectType, ...) \
  CJLU_DECLARE_TYPED_REGISTRY(                               \
      RegistryName, std::string, ObjectType, std::unique_ptr, ##__VA_ARGS__)

#define CJLU_DEFINE_REGISTRY(RegistryName, ObjectType, ...) \
  CJLU_DEFINE_TYPED_REGISTRY(                               \
      RegistryName, std::string, ObjectType, std::unique_ptr, ##__VA_ARGS__)

#define CJLU_DEFINE_REGISTRY_WITHOUT_WARNING(RegistryName, ObjectType, ...) \
  CJLU_DEFINE_TYPED_REGISTRY_WITHOUT_WARNING(                               \
      RegistryName, std::string, ObjectType, std::unique_ptr, ##__VA_ARGS__)

#define CJLU_DECLARE_SHARED_REGISTRY(RegistryName, ObjectType, ...) \
  CJLU_DECLARE_TYPED_REGISTRY(                                      \
      RegistryName, std::string, ObjectType, std::shared_ptr, ##__VA_ARGS__)

#define CJLU_DEFINE_SHARED_REGISTRY(RegistryName, ObjectType, ...) \
  CJLU_DEFINE_TYPED_REGISTRY(                                      \
      RegistryName, std::string, ObjectType, std::shared_ptr, ##__VA_ARGS__)

#define CJLU_DEFINE_SHARED_REGISTRY_WITHOUT_WARNING( \
    RegistryName, ObjectType, ...)                  \
  CJLU_DEFINE_TYPED_REGISTRY_WITHOUT_WARNING(        \
      RegistryName, std::string, ObjectType, std::shared_ptr, ##__VA_ARGS__)

// CJLU_REGISTER_CREATOR and CJLU_REGISTER_CLASS are hard-wired to use std::string
// as the key
// type, because that is the most commonly used cases.
#define CJLU_REGISTER_CREATOR(RegistryName, key, ...) \
  CJLU_REGISTER_TYPED_CREATOR(RegistryName, #key, __VA_ARGS__)

#define CJLU_REGISTER_CREATOR_WITH_PRIORITY(RegistryName, key, priority, ...) \
  CJLU_REGISTER_TYPED_CREATOR_WITH_PRIORITY(                                  \
      RegistryName, #key, priority, __VA_ARGS__)

#define CJLU_REGISTER_CLASS(RegistryName, key, ...) \
    CJLU_REGISTER_TYPED_CLASS(RegistryName, #key, __VA_ARGS__)

#define CJLU_REGISTER_CLASS_WITH_PRIORITY(RegistryName, key, priority, ...) \
  CJLU_REGISTER_TYPED_CLASS_WITH_PRIORITY(                                  \
      RegistryName, #key, priority, __VA_ARGS__)


CJLU_DECLARE_REGISTRY(
        CUDAOperatorRegistry,
        OperatorBase,
        const OperatorDef&,
        Workspace*);

#define REGISTER_CPU_OPERATOR(name, ...)                                   \
  CJLU_REGISTER_CLASS(CPUOperatorRegistry, name, __VA_ARGS__)

#define REGISTER_CPU_OPERATOR_STR(str_name, ...) \
  CJLU_REGISTER_TYPED_CLASS(CPUOperatorRegistry, str_name, __VA_ARGS__)

#define REGISTER_CPU_OPERATOR_WITH_ENGINE(name, engine, ...) \
  CJLU_REGISTER_CLASS(CPUOperatorRegistry, name##_ENGINE_##engine, __VA_ARGS__)

};


#endif //GENERAL_OCR_REGISTRYFACTORY_H
